---

title: 聚合函数
description: SQL中的聚合函数一共有 5 个，可以帮助我们求某列的最大值、最小值、平均值，条数、求和。
---
## 聚合函数有哪些
SQL 中的聚合函数一共包括 5 个，可以帮我们求某列的最大值、最小值和平均值等，它们分别是：

| 函数    | 说明   |
| ------- | ------ |
| COUNT() | 总行数 |
| MAX()   | 最大值 |
| MIN()   | 最小值 |
| SUM()   | 求和   |
| AVG()   | 平均值 |

```sql title='查询最大生命值大于 6000 的英雄数量'
mysql> SELECT COUNT(*) FROM heros WHERE hp_max > 6000;
+----------+
| COUNT(*) |
+----------+
|       41 |
+----------+
1 row in set (0.08 sec)
```

```sql title='如果想要查询最大生命值大于 6000，且有次要定位的英雄数量，需要使用 COUNT 函数'
mysql> SELECT COUNT(role_assist) FROM heros WHERE hp_max > 6000;
+--------------------+
| COUNT(role_assist) |
+--------------------+
|                 23 |
+--------------------+
1 row in set (0.00 sec)
```

COUNT(role_assist) 会忽略为 NULL 的数据行，而 COUNT(*) 只是统计数据行数，不管某个字段是否为 NULL。

```sql title='想要查询射手（主要定位或次要定位是射手）的最大生命值的最大值是多少，需要使用 MAX 函数'
mysql> SELECT MAX(hp_max) FROM heros WHERE role_main = '射手' or role_assist = '射手';
+-------------+
| MAX(hp_max) |
+-------------+
|        6014 |
+-------------+
1 row in set (0.01 sec)
```

```sql
mysql> SELECT COUNT(*),AVG(hp_max),MIN(attack_max), SUM(defense_max),MAX(mp_max) FROM heros WHERE role_main = '射手' or role_assist = '射手';
+----------+-------------+-----------------+------------------+-------------+
| COUNT(*) | AVG(hp_max) | MIN(attack_max) | SUM(defense_max) | MAX(mp_max) |
+----------+-------------+-----------------+------------------+-------------+
|       10 |      5798.5 |             362 |             3333 |        1784 |
+----------+-------------+-----------------+------------------+-------------+
1 row in set (0.00 sec)
```

需要说明的是 AVG 、MAX、MIN 等聚合函数会自动忽略值为 NULL 数据行， MAX 和  MAX 函数也可以用于字符串数据的统计，如果是英文字母，则按照 A-Z 的顺序排列，越往后，数值越大。



我们也可以对数据行中不同的取值进行聚合，先用 DISTINCT 函数取不同的数据，然后再使用聚合函数。

```sql title='查询不同的生命最大值的英雄数量是多少'
mysql> SELECT COUNT(DISTINCT hp_max) FROM heros;
+------------------------+
| COUNT(DISTINCT hp_max) |
+------------------------+
|                     61 |
+------------------------+
1 row in set (0.00 sec)
```



```sql title='统计不同生命最大值英雄的平均生命最大值，然后针对它们取平均值，保留小数点后两位'
mysql> SELECT ROUND(AVG(DISTINCT hp_max),2) FROM heros;
+-------------------------------+
| ROUND(AVG(DISTINCT hp_max),2) |
+-------------------------------+
|                       6653.84 |
+-------------------------------+
1 row in set (0.00 sec)
```
如果我们不使用 DISTINCT 函数，就是对全部数据进行聚合统计。如果使用了 DISTINCT 函数，就可以对数值不同的数据进行聚合。

## 如何对数据进行分组，并进行聚合统计

我们在做统计的时候，可能需要先对数据按照不同的数值进行分组，然后对这些分好的组进行聚合统计。对数据进行分组，需要使用 GROUP BY 子句。

```sql title='按照英雄的主要定位进行分组，并统计每组的英雄数量'
mysql> SELECT role_main , COUNT(*) FROM heros GROUP BY role_main;
+-----------+----------+
| role_main | COUNT(*) |
+-----------+----------+
| 坦克      |       10 |
| 战士      |       18 |
| 法师      |       19 |
| 辅助      |        6 |
| 射手      |       10 |
| 刺客      |        6 |
+-----------+----------+
6 rows in set (0.00 sec)
```

```sql title='对英雄按照次要定位进行分组，并统计每组英雄的数量'
mysql> SELECT role_assist, COUNT(*) FROM heros GROUP BY role_assist;
+-------------+----------+
| role_assist | COUNT(*) |
+-------------+----------+
| 战士        |        6 |
| 坦克        |       10 |
| 辅助        |        5 |
| NULL        |       40 |
| 法师        |        2 |
| 刺客        |        6 |
+-------------+----------+
6 rows in set (0.00 sec)
```

如果字段为 NULL ，也会被列为一个分组。在这个查询统计中，次要定位为 NULL，即只有一个主要定位的英雄是 40 个。

我们也可以使用多个字段进行分组，这就相当于把这些字段可能出现的所有取值情况都进行分组。

```sql titile='按照英雄的主要定位、次要定位进行分组，查看这些英雄的数量，并按照这些分组的英雄数量从高到低进行排序。'
mysql> SELECT role_main,role_assist,COUNT(*) num FROM heros GROUP BY role_main,role_assist ORDER BY num desc;
+-----------+-------------+-----+
| role_main | role_assist | num |
+-----------+-------------+-----+
| 法师      | NULL        |  12 |
| 射手      | NULL        |   9 |
| 战士      | NULL        |   8 |
| 战士      | 坦克        |   6 |
| 坦克      | NULL        |   5 |
| 坦克      | 辅助        |   3 |
| 辅助      | NULL        |   3 |
| 战士      | 刺客        |   3 |
| 刺客      | 战士        |   3 |
| 刺客      | NULL        |   3 |
| 坦克      | 战士        |   2 |
| 法师      | 坦克        |   2 |
| 辅助      | 坦克        |   2 |
| 法师      | 辅助        |   2 |
| 法师      | 刺客        |   2 |
| 战士      | 法师        |   1 |
| 辅助      | 法师        |   1 |
| 法师      | 战士        |   1 |
| 射手      | 刺客        |   1 |
+-----------+-------------+-----+
19 rows in set (0.00 sec)
```

## 使用 HAVING 进行过滤
当我们创建出很多分组的时候，有时候就需要对分组进行过滤。 过滤分组使用 HAVING ，而不是 SELECT 。
HAVING 的作用和 WHERE 一样，都是起到过滤的作用，只不过 WHERE 是用于数据行，而 HAVING 则作用于分组。

```sql title='按照英雄的主要定位、次要定位进行分组，并且筛选分组中英雄数量大于 5 的分组，最后按照分组中的英雄数量从高到低进行排序'
mysql> SELECT role_main , role_assist , COUNT(*) num FROM heros GROUP BY role_main,role_assist HAVING num > 5 ORDER BY n
um DESC;
+-----------+-------------+-----+
| role_main | role_assist | num |
+-----------+-------------+-----+
| 法师      | NULL        |  12 |
| 射手      | NULL        |   9 |
| 战士      | NULL        |   8 |
| 战士      | 坦克        |   6 |
+-----------+-------------+-----+
4 rows in set (0.00 sec)
```
如果把 HAVING 换成 WHERE ，SQL 会报错。对于分组的筛选，一定要使用 HAVING ，而不是 WHERE 。

**HAVING 支持所有 WHERE 的操作，因此所有需要 WHERE 子句实现的功能，都可以使用 HAVING 对分组进行筛选。**

```sql title='还是针对上一个例子的查询，增加一个过滤条件，即筛选最大生命值大于 6000 的英雄。'
mysql> SELECT role_main , role_assist , COUNT(*) num FROM heros WHERE hp_max > 6000 GROUP BY role_main,role_assist HAVING num > 5 ORDER BY num DESC;
+-----------+-------------+-----+
| role_main | role_assist | num |
+-----------+-------------+-----+
| 战士      | NULL        |   8 |
| 战士      | 坦克        |   6 |
+-----------+-------------+-----+
2 rows in set (0.00 sec)
```
这里就需要先使用 WHERE 子句对最大生命值大于 6000 的英雄进行条件过滤，然后再使用 GROUP BY 进行分组，使用 HAVING 进行分组的条件判断，然后使用 ORDER BY 进行排序。


## 总结
在一条 SELECT 语句中，可能会包含多个子句，用 WHERE 进行数据量的过滤，用 GROUP BY 进行分组，用 HAVING 进行分组过滤，用 ORDER BY 进行排序……

在 SELECT 查询中，关键字的顺序是不能颠倒的，它们的顺序是：

```sql
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... GROUP BY ...
```
使用 GROUP BY 进行分组，如果想让输出的结果有序，可以在 GROUP BY 后使用 ORDER BY ，因为 GROUP BY 只起到了分组的作用，排序还是通过 ORDER BY 来完成。

**聚合函数的计算在 GROUP BY之后，在 HAVING 之前。**
```sql title='筛选最大生命值大于 6000 的英雄，按照主要定位定位进行分组，选择分组英雄数量大于 5 的分组，按照分组英雄数从高到低进行排序，并显示每个分组的英雄数量、主要定位和平均最大生命值'
mysql> SELECT role_main,AVG(hp_max),COUNT(*) as num FROM heros WHERE hp_max > 6000 GROUP BY role_main HAVING num > 5 ORDER BY num DESC;
+-----------+-------------+-----+
| role_main | AVG(hp_max) | num |
+-----------+-------------+-----+
| 战士      |        7028 |  17 |
| 坦克      |      8312.4 |  10 |
| 法师      |        6417 |   6 |
+-----------+-------------+-----+
3 rows in set (0.00 sec)
```

```sql title='筛选最大生命值与最大法力值之和大于 7000 ，按照攻击范围来进行分组，显示分组的英雄数量，以及分组英雄的最大生命值与法力值之和的平均值、最大值和最小值，并按照分组英雄数从高到低进行排序，其中聚合函数的结果包括小数点后两位。'

```







```sh
curl "http://localhost/jarvis-project-app-web/control/login/login" ^
  -H "Accept: application/json" ^
  -H "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" ^
  -H "Cache-Control: no-cache" ^
  -H "Connection: keep-alive" ^
  -H "Content-Type: application/json;charset=UTF-8" ^
  -H "Origin: https://j-test.rmes.cn" ^
  -H "Pragma: no-cache" ^
  -H "Referer: https://j-test.rmes.cn/" ^
  -H "Sec-Fetch-Dest: empty" ^
  -H "Sec-Fetch-Mode: cors" ^
  -H "Sec-Fetch-Site: same-site" ^
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50" ^
  -H "product: jarvis" ^
  -H "sec-ch-ua: ^\^"Chromium^\^";v=^\^"110^\^", ^\^"Not A(Brand^\^";v=^\^"24^\^", ^\^"Microsoft Edge^\^";v=^\^"110^\^"" ^
  -H "sec-ch-ua-mobile: ?0" ^
  -H "sec-ch-ua-platform: ^\^"Windows^\^"" ^
  -H "userToken: null" ^
  --data-raw "^{^\^"userName^\^":^\^"18161971660^\^",^\^"password^\^":^\^"Z010719.^\^",^\^"loginWay^\^":^\^"1^\^"^}" ^
  --compressed
```

